// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_CXX11_TENSOR_TENSOR_CONTRACTION_MAPPER_H
#define EIGEN_CXX11_TENSOR_TENSOR_CONTRACTION_MAPPER_H

namespace Eigen {

namespace internal {

enum
{
	Rhs = 0,
	Lhs = 1
};

/*
 * Implementation of the Eigen blas_data_mapper class for tensors.
 */
/// The make pointer class is used by sycl in order to build the mapper class on the device. For other platform the
/// default make pointer is used which is scalar * for CoeffLoader.
template<typename Tensor, bool HasRawAccess, template<class> class MakePointer_ = MakePointer>
struct CoeffLoader;

template<typename Scalar,
		 typename Index,
		 int side,
		 typename Tensor,
		 typename nocontract_t,
		 typename contract_t,
		 int packet_size,
		 bool inner_dim_contiguous,
		 bool inner_dim_reordered,
		 int Alignment,
		 template<class> class MakePointer_ = MakePointer>
class BaseTensorContractionMapper;

template<typename Tensor, bool HasRawAccess, template<class> class MakePointer_>
struct CoeffLoader
{
	enum
	{
		DirectOffsets = false
	};

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE CoeffLoader(const Tensor& tensor)
		: m_tensor(tensor)
	{
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void offsetBuffer(typename Tensor::Index)
	{
		eigen_assert(false && "unsupported");
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE const typename MakePointer_<const typename Tensor::Scalar>::Type data() const
	{
		eigen_assert(false && "unsupported");
		return NULL;
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE typename Tensor::Scalar coeff(typename Tensor::Index index) const
	{
		return m_tensor.coeff(index);
	}

	template<int LoadMode>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename Tensor::PacketReturnType packet(typename Tensor::Index index) const
	{
		return m_tensor.template packet<LoadMode>(index);
	}

#ifdef EIGEN_USE_SYCL
	// The placeholder accessors require to be bound to a command group handler for SYCL
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void bind(cl::sycl::handler& cgh) const { m_tensor.bind(cgh); }
#endif

  private:
	const Tensor m_tensor;
};

template<typename Tensor, template<class> class MakePointer_>
struct CoeffLoader<Tensor, true, MakePointer_>
{
	enum
	{
		DirectOffsets = true
	};

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE CoeffLoader(const Tensor& tensor)
		: m_data(tensor.data())
	{
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void offsetBuffer(typename Tensor::Index offset) { m_data += offset; }

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE const typename MakePointer_<const typename Tensor::Scalar>::Type data() const
	{
		return m_data;
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE typename Tensor::Scalar coeff(typename Tensor::Index index) const
	{
		return loadConstant(m_data + index);
	}

	template<int LoadMode>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename Tensor::PacketReturnType packet(typename Tensor::Index index) const
	{
		return internal::ploadt_ro<typename Tensor::PacketReturnType, LoadMode>(m_data + index);
	}

#ifdef EIGEN_USE_SYCL
	// The placeholder accessors require to be bound to a command group handler for SYCL
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void bind(cl::sycl::handler& cgh) const { m_data.bind(cgh); }
#endif
  private:
	typedef typename Tensor::Scalar Scalar;

	typename MakePointer_<const Scalar>::Type m_data;
};

template<typename Scalar,
		 typename Index,
		 int side,
		 typename Tensor,
		 typename nocontract_t,
		 typename contract_t,
		 int packet_size,
		 bool inner_dim_contiguous,
		 int Alignment,
		 template<class> class MakePointer_ = MakePointer>
class SimpleTensorContractionMapper
{
  public:
	EIGEN_DEVICE_FUNC
	SimpleTensorContractionMapper(const Tensor& tensor,
								  const nocontract_t& nocontract_strides,
								  const nocontract_t& ij_strides,
								  const contract_t& contract_strides,
								  const contract_t& k_strides)
		: m_tensor(tensor)
		, m_nocontract_strides(nocontract_strides)
		, m_ij_strides(ij_strides)
		, m_contract_strides(contract_strides)
		, m_k_strides(k_strides)
	{
	}

	enum
	{
		DirectOffsets = CoeffLoader<Tensor, Tensor::RawAccess, MakePointer_>::DirectOffsets
	};

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void offsetBuffer(typename Tensor::Index offset)
	{
		m_tensor.offsetBuffer(offset);
	}

	EIGEN_DEVICE_FUNC
	EIGEN_STRONG_INLINE void prefetch(Index /*i*/) {}

	EIGEN_DEVICE_FUNC
	EIGEN_STRONG_INLINE Scalar operator()(Index row) const
	{
		// column major assumption
		return operator()(row, 0);
	}

	EIGEN_DEVICE_FUNC
	EIGEN_STRONG_INLINE Scalar operator()(Index row, Index col) const { return m_tensor.coeff(computeIndex(row, col)); }

	EIGEN_DEVICE_FUNC
	EIGEN_STRONG_INLINE Index computeIndex(Index row, Index col) const
	{
		const bool left = (side == Lhs);
		EIGEN_UNUSED_VARIABLE(left); // annoying bug in g++8.1: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85963
		Index nocontract_val = left ? row : col;
		Index linidx = 0;
		EIGEN_UNROLL_LOOP
		for (int i = static_cast<int>(array_size<nocontract_t>::value) - 1; i > 0; i--) {
			const Index idx = nocontract_val / m_ij_strides[i];
			linidx += idx * m_nocontract_strides[i];
			nocontract_val -= idx * m_ij_strides[i];
		}
		if (array_size<typename Tensor::Dimensions>::value > array_size<contract_t>::value) {
			if (side == Lhs && inner_dim_contiguous) {
				eigen_assert(m_nocontract_strides[0] == 1);
				linidx += nocontract_val;
			} else {
				linidx += nocontract_val * m_nocontract_strides[0];
			}
		}

		Index contract_val = left ? col : row;
		if (array_size<contract_t>::value > 0) {
			EIGEN_UNROLL_LOOP
			for (int i = static_cast<int>(array_size<contract_t>::value) - 1; i > 0; i--) {
				const Index idx = contract_val / m_k_strides[i];
				linidx += idx * m_contract_strides[i];
				contract_val -= idx * m_k_strides[i];
			}

			if (side == Rhs && inner_dim_contiguous) {
				eigen_assert(m_contract_strides[0] == 1);
				linidx += contract_val;
			} else {
				linidx += contract_val * m_contract_strides[0];
			}
		}

		return linidx;
	}

	EIGEN_DEVICE_FUNC
	EIGEN_STRONG_INLINE IndexPair<Index> computeIndexPair(Index row, Index col, const Index distance) const
	{
		const bool left = (side == Lhs);
		EIGEN_UNUSED_VARIABLE(left); // annoying bug in g++8.1: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85963
		Index nocontract_val[2] = { left ? row : col, left ? row + distance : col };
		Index linidx[2] = { 0, 0 };
		if (array_size<typename Tensor::Dimensions>::value > array_size<contract_t>::value) {
			EIGEN_UNROLL_LOOP
			for (int i = static_cast<int>(array_size<nocontract_t>::value) - 1; i > 0; i--) {
				const Index idx0 = nocontract_val[0] / m_ij_strides[i];
				const Index idx1 = nocontract_val[1] / m_ij_strides[i];
				linidx[0] += idx0 * m_nocontract_strides[i];
				linidx[1] += idx1 * m_nocontract_strides[i];
				nocontract_val[0] -= idx0 * m_ij_strides[i];
				nocontract_val[1] -= idx1 * m_ij_strides[i];
			}
			if (side == Lhs && inner_dim_contiguous) {
				eigen_assert(m_nocontract_strides[0] == 1);
				linidx[0] += nocontract_val[0];
				linidx[1] += nocontract_val[1];
			} else {
				linidx[0] += nocontract_val[0] * m_nocontract_strides[0];
				linidx[1] += nocontract_val[1] * m_nocontract_strides[0];
			}
		}

		Index contract_val[2] = { left ? col : row, left ? col : row + distance };
		if (array_size<contract_t>::value > 0) {
			EIGEN_UNROLL_LOOP
			for (int i = static_cast<int>(array_size<contract_t>::value) - 1; i > 0; i--) {
				const Index idx0 = contract_val[0] / m_k_strides[i];
				const Index idx1 = contract_val[1] / m_k_strides[i];
				linidx[0] += idx0 * m_contract_strides[i];
				linidx[1] += idx1 * m_contract_strides[i];
				contract_val[0] -= idx0 * m_k_strides[i];
				contract_val[1] -= idx1 * m_k_strides[i];
			}

			if (side == Rhs && inner_dim_contiguous) {
				eigen_assert(m_contract_strides[0] == 1);
				linidx[0] += contract_val[0];
				linidx[1] += contract_val[1];
			} else {
				linidx[0] += contract_val[0] * m_contract_strides[0];
				linidx[1] += contract_val[1] * m_contract_strides[0];
			}
		}
		return IndexPair<Index>(linidx[0], linidx[1]);
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE Index firstAligned(Index size) const
	{
		// Only claim alignment when we can compute the actual stride (ie when we're
		// dealing with the lhs with inner_dim_contiguous. This is because the
		// matrix-vector product relies on the stride when dealing with aligned inputs.
		return (Alignment == Aligned) && (side == Lhs) && inner_dim_contiguous ? 0 : size;
	}
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE Index stride() const
	{
		return ((side == Lhs) && inner_dim_contiguous && array_size<contract_t>::value > 0) ? m_contract_strides[0] : 1;
	}

#ifdef EIGEN_USE_SYCL
	// The placeholder accessors require to be bound to a command group handler for SYCL
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void bind(cl::sycl::handler& cgh) const { m_tensor.bind(cgh); }
#endif

	const CoeffLoader<Tensor, Tensor::RawAccess, MakePointer_>& tensor() const { return m_tensor; }

	const nocontract_t& nocontract_strides() const { return m_nocontract_strides; }
	const nocontract_t& ij_strides() const { return m_ij_strides; }
	const contract_t& contract_strides() const { return m_contract_strides; }
	const contract_t& k_strides() const { return m_k_strides; }

  protected:
	CoeffLoader<Tensor, Tensor::RawAccess, MakePointer_> m_tensor;
	const nocontract_t m_nocontract_strides;
	const nocontract_t m_ij_strides;
	const contract_t m_contract_strides;
	const contract_t m_k_strides;
};

template<typename Scalar,
		 typename Index,
		 int side,
		 typename Tensor,
		 typename nocontract_t,
		 typename contract_t,
		 int packet_size,
		 bool inner_dim_contiguous,
		 bool inner_dim_reordered,
		 int Alignment,
		 template<class>
		 class MakePointer_>
class BaseTensorContractionMapper
	: public SimpleTensorContractionMapper<Scalar,
										   Index,
										   side,
										   Tensor,
										   nocontract_t,
										   contract_t,
										   packet_size,
										   inner_dim_contiguous,
										   Alignment,
										   MakePointer_>
{
  public:
	typedef SimpleTensorContractionMapper<Scalar,
										  Index,
										  side,
										  Tensor,
										  nocontract_t,
										  contract_t,
										  packet_size,
										  inner_dim_contiguous,
										  Alignment,
										  MakePointer_>
		ParentMapper;

	EIGEN_DEVICE_FUNC
	BaseTensorContractionMapper(const Tensor& tensor,
								const nocontract_t& nocontract_strides,
								const nocontract_t& ij_strides,
								const contract_t& contract_strides,
								const contract_t& k_strides)
		: ParentMapper(tensor, nocontract_strides, ij_strides, contract_strides, k_strides)
	{
	}

	template<typename PacketT, int AlignmentType>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
		typename internal::enable_if<internal::unpacket_traits<PacketT>::size == packet_size, PacketT>::type
		load(Index i, Index j) const
	{
		// whole method makes column major assumption

		// don't need to add offsets for now (because operator handles that)
		// current code assumes packet size must be a multiple of 2
		EIGEN_STATIC_ASSERT(packet_size % 2 == 0, YOU_MADE_A_PROGRAMMING_MISTAKE);

		if (Tensor::PacketAccess && inner_dim_contiguous && !inner_dim_reordered) {
			const Index index = this->computeIndex(i, j);
			eigen_assert(this->computeIndex(i + packet_size - 1, j) == index + packet_size - 1);
			return this->m_tensor.template packet<AlignmentType>(index);
		}

		const IndexPair<Index> indexPair = this->computeIndexPair(i, j, packet_size - 1);
		const Index first = indexPair.first;
		const Index lastIdx = indexPair.second;

		// We can always do optimized packet reads from left hand side right now, because
		// the vertical matrix dimension on the left hand side is never contracting.
		// On the right hand side we need to check if the contracting dimensions may have
		// been shuffled first.
		if (Tensor::PacketAccess &&
			(side == Lhs || internal::array_size<contract_t>::value <= 1 || !inner_dim_reordered) &&
			(lastIdx - first) == (packet_size - 1)) {

			return this->m_tensor.template packet<AlignmentType>(first);
		}

		EIGEN_ALIGN_MAX Scalar data[packet_size];

		data[0] = this->m_tensor.coeff(first);
		EIGEN_UNROLL_LOOP
		for (Index k = 1; k < packet_size - 1; k += 2) {
			const IndexPair<Index> internal_pair = this->computeIndexPair(i + k, j, 1);
			data[k] = this->m_tensor.coeff(internal_pair.first);
			data[k + 1] = this->m_tensor.coeff(internal_pair.second);
		}
		data[packet_size - 1] = this->m_tensor.coeff(lastIdx);

		return pload<PacketT>(data);
	}

	template<typename PacketT, int AlignmentType>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
		typename internal::enable_if<internal::unpacket_traits<PacketT>::size != packet_size, PacketT>::type
		load(Index i, Index j) const
	{
		const Index requested_packet_size = internal::unpacket_traits<PacketT>::size;
		EIGEN_ALIGN_MAX Scalar data[requested_packet_size];

		const IndexPair<Index> indexPair = this->computeIndexPair(i, j, requested_packet_size - 1);
		const Index first = indexPair.first;
		const Index lastIdx = indexPair.second;

		data[0] = this->m_tensor.coeff(first);
		for (Index k = 1; k < requested_packet_size - 1; k += 2) {
			const IndexPair<Index> internal_pair = this->computeIndexPair(i + k, j, 1);
			data[k] = this->m_tensor.coeff(internal_pair.first);
			data[k + 1] = this->m_tensor.coeff(internal_pair.second);
		}
		data[requested_packet_size - 1] = this->m_tensor.coeff(lastIdx);

		return pload<PacketT>(data);
	}

	template<typename PacketT, int AlignmentType>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PacketT loadPacket(Index i, Index j) const
	{
		return this->load<PacketT, AlignmentType>(i, j);
	}
};

template<typename Scalar,
		 typename Index,
		 int side,
		 typename Tensor,
		 typename nocontract_t,
		 typename contract_t,
		 bool inner_dim_contiguous,
		 bool inner_dim_reordered,
		 int Alignment,
		 template<class>
		 class MakePointer_>
class BaseTensorContractionMapper<Scalar,
								  Index,
								  side,
								  Tensor,
								  nocontract_t,
								  contract_t,
								  1,
								  inner_dim_contiguous,
								  inner_dim_reordered,
								  Alignment,
								  MakePointer_>
	: public SimpleTensorContractionMapper<Scalar,
										   Index,
										   side,
										   Tensor,
										   nocontract_t,
										   contract_t,
										   1,
										   inner_dim_contiguous,
										   Alignment,
										   MakePointer_>
{
  public:
	typedef SimpleTensorContractionMapper<Scalar,
										  Index,
										  side,
										  Tensor,
										  nocontract_t,
										  contract_t,
										  1,
										  inner_dim_contiguous,
										  Alignment,
										  MakePointer_>
		ParentMapper;

	EIGEN_DEVICE_FUNC
	BaseTensorContractionMapper(const Tensor& tensor,
								const nocontract_t& nocontract_strides,
								const nocontract_t& ij_strides,
								const contract_t& contract_strides,
								const contract_t& k_strides)
		: ParentMapper(tensor, nocontract_strides, ij_strides, contract_strides, k_strides)
	{
	}

	template<typename PacketT, int>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PacketT loadPacket(Index i, Index j) const
	{
		EIGEN_ALIGN_MAX Scalar data[1];
		data[0] = this->m_tensor.coeff(this->computeIndex(i, j));
		return pload<PacketT>(data);
	}
	template<typename PacketT, int>
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PacketT load(Index i, Index j) const
	{
		EIGEN_ALIGN_MAX Scalar data[1];
		data[0] = this->m_tensor.coeff(this->computeIndex(i, j));
		return pload<PacketT>(data);
	}
};

template<typename Scalar,
		 typename Index,
		 int side,
		 typename Tensor,
		 typename nocontract_t,
		 typename contract_t,
		 int packet_size,
		 bool inner_dim_contiguous,
		 bool inner_dim_reordered,
		 int Alignment,
		 template<class> class MakePointer_ = MakePointer>
class TensorContractionSubMapper
{
  public:
	typedef BaseTensorContractionMapper<Scalar,
										Index,
										side,
										Tensor,
										nocontract_t,
										contract_t,
										packet_size,
										inner_dim_contiguous,
										inner_dim_reordered,
										Alignment,
										MakePointer_>
		ParentMapper;
	typedef TensorContractionSubMapper<Scalar,
									   Index,
									   side,
									   Tensor,
									   nocontract_t,
									   contract_t,
									   packet_size,
									   inner_dim_contiguous,
									   inner_dim_reordered,
									   Alignment,
									   MakePointer_>
		Self;
	typedef Self LinearMapper;

	enum
	{
		// We can use direct offsets iff the parent mapper supports then and we can compute the strides.
		// TODO: we should also enable direct offsets for the Rhs case.
		UseDirectOffsets =
			ParentMapper::DirectOffsets && (side == Lhs) && inner_dim_contiguous && (array_size<contract_t>::value > 0)
	};

	EIGEN_DEVICE_FUNC TensorContractionSubMapper(const ParentMapper& base_mapper, Index vert_offset, Index horiz_offset)
		: m_base_mapper(base_mapper)
		, m_vert_offset(vert_offset)
		, m_horiz_offset(horiz_offset)
	{
		// Bake the offsets into the buffer used by the base mapper whenever possible. This avoids the need to recompute
		// this offset every time we attempt to access a coefficient.
		if (UseDirectOffsets) {
			Index stride = m_base_mapper.stride();
			m_base_mapper.offsetBuffer(vert_offset + horiz_offset * stride);
		}
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE Scalar operator()(Index i) const
	{
		if (UseDirectOffsets) {
			return m_base_mapper(i, 0);
		}
		return m_base_mapper(i + m_vert_offset, m_horiz_offset);
	}
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE Scalar operator()(Index i, Index j) const
	{
		if (UseDirectOffsets) {
			return m_base_mapper(i, j);
		}
		return m_base_mapper(i + m_vert_offset, j + m_horiz_offset);
	}

	template<typename PacketT>
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE PacketT loadPacket(Index i) const
	{
		if (UseDirectOffsets) {
			return m_base_mapper.template loadPacket<PacketT, Alignment>(i, 0);
		}
		return m_base_mapper.template loadPacket<PacketT, Alignment>(i + m_vert_offset, m_horiz_offset);
	}

	template<typename PacketT>
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE PacketT loadPacket(Index i, Index j) const
	{
		if (UseDirectOffsets) {
			return m_base_mapper.template loadPacket<PacketT, Alignment>(i, j);
		}
		return m_base_mapper.template loadPacket<PacketT, Alignment>(i + m_vert_offset, j + m_horiz_offset);
	}

	template<typename PacketT, int AlignmentType>
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE PacketT loadPacket(Index i, Index j) const
	{
		if (UseDirectOffsets) {
			return m_base_mapper.template load<PacketT, AlignmentType>(i, j);
		}
		return m_base_mapper.template loadPacket<PacketT, AlignmentType>(i + m_vert_offset, j + m_horiz_offset);
	}

	template<typename PacketT>
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void storePacket(Index i, const PacketT& p) const
	{
		if (UseDirectOffsets) {
			m_base_mapper.storePacket(i, 0, p);
		}
		m_base_mapper.storePacket(i + m_vert_offset, m_horiz_offset, p);
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE LinearMapper getLinearMapper(Index i, Index j) const
	{
		if (UseDirectOffsets) {
			return LinearMapper(m_base_mapper, i, j);
		}
		return LinearMapper(m_base_mapper, i + m_vert_offset, j + m_horiz_offset);
	}

	template<typename PacketT, int AlignmentType>
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE PacketT load(Index i) const
	{
		EIGEN_STATIC_ASSERT((internal::is_same<PacketT, PacketT>::value), YOU_MADE_A_PROGRAMMING_MISTAKE);
		const int ActualAlignment = (AlignmentType == Aligned) && (Alignment == Aligned) ? Aligned : Unaligned;
		if (UseDirectOffsets) {
			return m_base_mapper.template loadPacket<PacketT, ActualAlignment>(i, 0);
		}
		return m_base_mapper.template loadPacket<PacketT, ActualAlignment>(i + m_vert_offset, m_horiz_offset);
	}

	template<typename PacketT>
	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE bool aligned(Index) const
	{
		return false;
	}

#ifdef EIGEN_USE_SYCL
	// The placeholder accessors require to be bound to a command group handler for SYCL
	EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void bind(cl::sycl::handler& cgh) const { m_base_mapper.bind(cgh); }
#endif

	const ParentMapper& base_mapper() const { return m_base_mapper; }
	Index vert_offset() const { return m_vert_offset; }
	Index horiz_offset() const { return m_horiz_offset; }

  private:
	ParentMapper m_base_mapper;
	const Index m_vert_offset;
	const Index m_horiz_offset;
};

template<typename Scalar_,
		 typename Index,
		 int side,
		 typename Tensor,
		 typename nocontract_t,
		 typename contract_t,
		 int packet_size,
		 bool inner_dim_contiguous,
		 bool inner_dim_reordered,
		 int Alignment,
		 template<class> class MakePointer_ = MakePointer>
class TensorContractionInputMapper
	: public BaseTensorContractionMapper<Scalar_,
										 Index,
										 side,
										 Tensor,
										 nocontract_t,
										 contract_t,
										 packet_size,
										 inner_dim_contiguous,
										 inner_dim_reordered,
										 Alignment,
										 MakePointer_>
{

  public:
	typedef Scalar_ Scalar;
	typedef BaseTensorContractionMapper<Scalar,
										Index,
										side,
										Tensor,
										nocontract_t,
										contract_t,
										packet_size,
										inner_dim_contiguous,
										inner_dim_reordered,
										Alignment,
										MakePointer_>
		Base;
	typedef TensorContractionSubMapper<Scalar,
									   Index,
									   side,
									   Tensor,
									   nocontract_t,
									   contract_t,
									   packet_size,
									   inner_dim_contiguous,
									   inner_dim_reordered,
									   Alignment,
									   MakePointer_>
		SubMapper;
	typedef SubMapper VectorMapper;

	EIGEN_DEVICE_FUNC TensorContractionInputMapper(const Tensor& tensor,
												   const nocontract_t& nocontract_strides,
												   const nocontract_t& ij_strides,
												   const contract_t& contract_strides,
												   const contract_t& k_strides)
		: Base(tensor, nocontract_strides, ij_strides, contract_strides, k_strides)
	{
	}

	EIGEN_DEVICE_FUNC
	EIGEN_STRONG_INLINE SubMapper getSubMapper(Index i, Index j) const { return SubMapper(*this, i, j); }

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE VectorMapper getVectorMapper(Index i, Index j) const
	{
		return VectorMapper(*this, i, j);
	}

	EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE const CoeffLoader<Tensor, Tensor::RawAccess, MakePointer_>& get_tensor() const
	{
		return Base::m_tensor;
	}
};

template<typename T>
struct TensorContractionInputMapperTrait;

template<typename Scalar_,
		 typename Index_,
		 int side_,
		 typename Tensor_,
		 typename nocontract_t_,
		 typename contract_t_,
		 int packet_size_,
		 bool inner_dim_contiguous_,
		 bool inner_dim_reordered_,
		 int Alignment_,
		 template<class>
		 class MakePointer_>
struct TensorContractionInputMapperTrait<TensorContractionInputMapper<Scalar_,
																	  Index_,
																	  side_,
																	  Tensor_,
																	  nocontract_t_,
																	  contract_t_,
																	  packet_size_,
																	  inner_dim_contiguous_,
																	  inner_dim_reordered_,
																	  Alignment_,
																	  MakePointer_>>
{

	typedef Tensor_ XprType;
	static const bool inner_dim_contiguous = inner_dim_contiguous_;
	static const bool inner_dim_reordered = inner_dim_reordered_;
};

} // end namespace internal
} // end namespace Eigen

#endif // EIGEN_CXX11_TENSOR_TENSOR_CONTRACTION_MAPPER_H
